/**
 * 
 */
package com.roshan.web;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;

import javax.servlet.http.HttpServletRequest;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import com.roshan.domain.LabelValue;
import com.roshan.domain.User;
import com.roshan.service.INotificationService;
import com.roshan.util.CommonConstants;
import com.roshan.util.LabelValueComparator;
import com.roshan.util.RequestUtil;

/**
 * @author roshan
 * 
 */
@Controller
@SuppressWarnings("nls")
public class SignUpController {

    private final Logger logger = Logger.getLogger(getClass());

    @Autowired
    private MessageSource messageSource;

    @Autowired
    private INotificationService notificationService;

    @RequestMapping(value = "/register", method = RequestMethod.GET)
    public static String showRegisterForm(Model uiModel, Locale locale) {
        uiModel.addAttribute("user", new User());
        uiModel.addAttribute("countries", buildCountryList(locale));
        return "register";
    }

    @RequestMapping(value = "/register", method = RequestMethod.POST)
    public String submitRegisterForm(User user, BindingResult error, HttpServletRequest request, Model uiModel,
            Locale locale) {

        try {
            String appName = this.messageSource.getMessage("application_name", null, locale);
            String message = this.messageSource.getMessage("signup.email.message", new Object[] {appName}, locale);

            String email = user.getEmail();
            String fullName = user.getFullName();
            String username = user.getUsername();
            String notMaskedPassword = user.getNotMaskedPassword();
            String appURL = RequestUtil.getAppURL(request);
            String templateName = "accountCreated.vm";

            if (request.getParameter("cancel") != null) {
                return "redirect:/";

            }

            user.getAddress().setCountry(request.getParameter("country"));

            uiModel.addAttribute("user", user);
            uiModel.addAttribute("countries", buildCountryList(locale));

            validateUser(user, error);

            if (!error.hasErrors()) {

                user.persist();
                createSecurityContext(user);
                this.notificationService.sendSignUpSuccessMail(email, fullName, username, notMaskedPassword, appName,
                        message, appURL, templateName, locale);
                return "registerSuccess";
            }

        } catch (Exception e) {
            this.logger.error(e.getMessage(), e);
            uiModel.addAttribute("errorMessage", this.messageSource.getMessage("signup.error", null, locale));
        }

        return "register";
    }

    @RequestMapping(value = "/registerSuccess", method = RequestMethod.GET)
    public static String showRegisterSuccess() {
        return "redirect:/";
    }

    /**
     * Creates the security context.
     * 
     * @param user the user
     */
    private static void createSecurityContext(User user) {

        UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(user.getUsername(),
                user.getConfirmPassword(), user.getAuthorities());
        auth.setDetails(user);
        SecurityContextHolder.getContext().setAuthentication(auth);

    }

    /**
     * Validate user.
     * 
     * @param user the user
     * @param error the error
     */
    private static void validateUser(User user, BindingResult error) {

        validateUsername(user, error);
        validatePassword(user, error);
        validateConfirmPassword(user, error);
        validatePasswordHint(user, error);
        validateFirstName(user, error);
        validateLastName(user, error);
        validateEmail(user, error);
        validatePhoneNumber(user, error);
        validateWebsite(user, error);
        validateAddress(user, error);
        validateCity(user, error);
        validateZip(user, error);

    }

    /**
     * Validate zip.
     * 
     * @param user the user
     * @param error the error
     */
    private static void validateZip(User user, BindingResult error) {
        if (!StringUtils.hasText(user.getAddress().getPostalCode())) {
            error.rejectValue("address.postalCode", "signup.address.postalCode.empty");
        } else if (!user.getAddress().getPostalCode().trim().matches(CommonConstants.ZIP_REGEXP)) {
            error.rejectValue("address.postalCode", "signup.address.postalCode.invalid");
        }

    }

    /**
     * Validate city.
     * 
     * @param user the user
     * @param error the error
     */
    private static void validateCity(User user, BindingResult error) {
        if (!StringUtils.hasText(user.getAddress().getCity())) {
            error.rejectValue("address.city", "signup.address.city.empty");
        }

    }

    /**
     * Validate address.
     * 
     * @param user the user
     * @param error the error
     */
    private static void validateAddress(User user, BindingResult error) {
        if (!StringUtils.hasText(user.getAddress().getAddress())) {
            error.rejectValue("address.address", "signup.address.address.empty");
        }

    }

    /**
     * Validate website.
     * 
     * @param user the user
     * @param error the error
     */
    private static void validateWebsite(User user, BindingResult error) {
        if (StringUtils.hasText(user.getWebsite()) && !user.getWebsite().trim().matches(CommonConstants.WEB_URL_REGEXP)) {
            error.rejectValue("website", "signup.website.invalid");

        }
    }

    /**
     * Validate phone number.
     * 
     * @param user the user
     * @param error the error
     */
    private static void validatePhoneNumber(User user, BindingResult error) {
        if (StringUtils.hasText(user.getPhoneNumber())) {
            if (!user.getPhoneNumber().trim().matches(CommonConstants.MOBILE_NUMBER_REGEXP)) {
                error.rejectValue("phoneNumber", "signup.phonenumber.invalid");
            } else if (user.getPhoneNumber().trim().length() != 10) {
                error.rejectValue("phoneNumber", "signup.phonenumber.invalid.length", "");
            }
        }
    }

    /**
     * Validate email.
     * 
     * @param user the user
     * @param error the error
     */
    private static void validateEmail(User user, BindingResult error) {
        if (!StringUtils.hasText(user.getEmail())) {
            error.rejectValue("email", "signup.email.empty");
        } else if (!user.getEmail().trim().matches(CommonConstants.EMAIL_REGEXP)
                || org.springframework.util.StringUtils.containsWhitespace(user.getEmail().trim())) {
            error.rejectValue("email", "signup.email.invalid");
        } else if (!User.findUserByEmail(user.getEmail()).getResultList().isEmpty()) {
            error.rejectValue("email", "signup.email.not.available");
        }
    }

    /**
     * Validate last name.
     * 
     * @param user the user
     * @param error the error
     */
    private static void validateLastName(User user, BindingResult error) {
        if (!StringUtils.hasText(user.getLastName())) {
            error.rejectValue("lastName", "signup.lastname.empty");
        } else if (!user.getLastName().trim().matches(CommonConstants.LAST_NAME_REGEXP)
                || org.springframework.util.StringUtils.containsWhitespace(user.getLastName().trim())) {
            error.rejectValue("lastName", "signup.lastname.invalid");
        }
    }

    /**
     * Validate first name.
     * 
     * @param user the user
     * @param error the error
     */
    private static void validateFirstName(User user, BindingResult error) {
        if (!StringUtils.hasText(user.getFirstName())) {
            error.rejectValue("firstName", "signup.firstname.empty");
        } else if (!user.getFirstName().trim().matches(CommonConstants.FIRST_NAME_REGEXP)
                || org.springframework.util.StringUtils.containsWhitespace(user.getFirstName().trim())) {
            error.rejectValue("firstName", "signup.firstname.invalid");
        }
    }

    /**
     * Validate password hint.
     * 
     * @param user the user
     * @param error the error
     */
    private static void validatePasswordHint(User user, BindingResult error) {
        if (!StringUtils.hasText(user.getPasswordHint())) {
            error.rejectValue("passwordHint", "signup.passwordhint.empty");
        }
    }

    /**
     * Validate confirm password.
     * 
     * @param user the user
     * @param error the error
     */
    private static void validateConfirmPassword(User user, BindingResult error) {
        if (!StringUtils.hasText(user.getConfirmPassword())) {
            error.rejectValue("confirmPassword", "signup.confirmpassword.empty");
        } else if (StringUtils.hasText(user.getNotMaskedPassword())
                && !user.getConfirmPassword().equals(user.getNotMaskedPassword())) {
            error.rejectValue("confirmPassword", "signup.password.and.confirm.password.mismatch");

        }
    }

    /**
     * Validate password.
     * 
     * @param user the user
     * @param error the error
     */
    private static void validatePassword(User user, BindingResult error) {
        if (!StringUtils.hasText(user.getNotMaskedPassword())) {
            error.rejectValue("password", "signup.password.empty");
        } else if (user.getNotMaskedPassword().trim().length() < CommonConstants.MIN_PASSWORD_LENGTH
                || user.getNotMaskedPassword().trim().length() > CommonConstants.MAX_PASSWORD_LENGTH) {
            error.rejectValue("password", "signup.password.invalid.length");
        } else if (!user.getNotMaskedPassword().trim().matches(CommonConstants.PASSWORD_REGEXP)) {
            error.rejectValue("password", "signup.password.invalid");
        }
    }

    /**
     * Validate username.
     * 
     * @param user the user
     * @param error the error
     */
    private static void validateUsername(User user, BindingResult error) {
        if (!StringUtils.hasText(user.getUsername())) {
            error.rejectValue("username", "signup.username.empty");
        } else if (user.getUsername().trim().length() < CommonConstants.MIN_USERNAME_LENGTH
                || user.getUsername().trim().length() > CommonConstants.MAX_USERNAME_LENGTH) {
            error.rejectValue("username", "signup.username.invalid.length");
        } else if (!user.getUsername().trim().matches(CommonConstants.USER_NAME_REGEXP)) {
            error.rejectValue("username", "signup.username.invalid");
        } else if (!User.findUserByUsername(user.getUsername()).getResultList().isEmpty()) {
            error.rejectValue("username", "signup.username.not.available");
        }
    }

    /**
     * Builds the country list.
     * 
     * @param locale the locale
     * @return the list
     */
    @SuppressWarnings("unchecked")
    private static List<LabelValue> buildCountryList(Locale locale) {
        final String EMPTY = "";
        final Locale[] available = Locale.getAvailableLocales();

        List<LabelValue> countries = new ArrayList<LabelValue>();

        for (int i = 0; i < available.length; i++) {
            final String iso = available[i].getCountry();
            final String name = available[i].getDisplayCountry(locale);

            if (!EMPTY.equals(iso) && !EMPTY.equals(name)) {
                LabelValue country = new LabelValue(name, iso);

                if (!countries.contains(country)) {
                    countries.add(new LabelValue(name, iso));
                }
            }
        }

        Collections.sort(countries, new LabelValueComparator(locale));

        return countries;
    }

}
